#include "jam.h"
#include "alloc.h"
#include "hash.h"
#include "lists.h"
#include "newstr.h"
#include "regexp.h"
#include "headers.h"
#include "cache.h"
#include "depslib.h"

extern struct depsStats g_stats;
struct hash *headerhash = 0;
static regexp *hdrre = 0;
static regexp *dimpre = 0;

LIST *headers1( const char *file, int depth ) {
  FILE *f;
  regexp *re;
  LIST *result = 0;
  char buf[1024];
  int fnlen = strlen( file );
  int dMode = 0;
  int dState = 0;
  if( file[fnlen - 2] == '.' && file[fnlen - 1] == 'd' ) {
    dMode = 1;
    if( DEBUG_HEADER ) {
      printf( "D file detected\n" );
    }
  }
  if( DEBUG_HEADER ) {
    printf( "header open %s\n", file );
  }
  if( !( f = fopen( file, "r" ) ) ) {
    return result;
  }
  if( DEBUG_HEADER ) {
    printf( "header scan %s\n", file );
  }
  if( !hdrre ) {
    hdrre = my_regcomp( "^[ 	]*#[ 	]*include[ 	]*([<\"])([^\">]*)([\">]).*$" );
  }
  re = hdrre;
  if( dMode ) {
    if( !dimpre )
      dimpre = my_regcomp(
                 "^.*import[ \t]*([[A-Za-z_ \t]+=[ \t]*)?([A-Za-z_\\.]+)(\\:.+)?;.*$" );
    re = dimpre;
  }
  while( fgets( buf, sizeof( buf ), f ) ) {
    if( dMode ) {
      if( dState == 0 ) {
        if( strstr( buf, "public" ) ) {
          dState = 1;
        }
      }
      if( dState >= 1 ) {
        if( strchr( buf, '{' ) ) {
          ++dState;
        }
        if( strchr( buf, '}' ) ) {
          --dState;
        }
      }
    }
    if( strstr( buf, dMode ? "import" : "include" ) )
      if( my_regexec( re, buf ) ) {
        char buf2[MAXSYM];
        if( !dMode && re->startp[3] ) {
          int l = re->endp[3] - re->startp[1];
          memcpy( buf2, re->startp[1], l );
          buf2[l] = '\0';
        } else if( re->startp[2] ) {
          if( depth > 0 ) {
            if( dState == 0 )
            { continue; }
          }
          buf2[0] = '<';
          const char* p;
          int j = 0;
          for( p = re->startp[2]; p < re->endp[2]; ++p ) {
            char c = *p;
            ++j;
            if( c == '.' )
            { buf2[j] = '/'; }
            else
            { buf2[j] = c; }
          }
          buf2[++j] = '.';
          buf2[++j] = 'd';
          buf2[++j] = '>';
          buf2[++j] = '\0';
        }
        result = list_new( result, buf2, 0 );
        if( DEBUG_HEADER ) {
          printf( "header found: %s\n", buf2 );
        }
      }
    if( dMode ) {
      if( dState == 1 ) {
        if( strchr( buf, ';' ) ) {
          dState = 0;
        }
        if( strchr( buf, '}' ) ) {
          dState = 0;
        }
      }
    }
  }
  fclose( f );
  g_stats.scanned++;
  return result;
}

static ALLOC *hdralloc = 0;

HEADERS *headerentry( HEADERS *chain, HEADER *header ) {
  HEADERS *c;
  if( !hdralloc ) {
    hdralloc = alloc_init( sizeof( HEADERS ), 64 );
  }
  c = ( HEADERS * ) alloc_enter( hdralloc );
  c->header = header;
  if( !chain ) {
    chain = c;
  } else {
    chain->tail->next = c;
  }
  chain->tail = c;
  c->next = 0;
  return chain;
}

HEADER *headersDepth( const char *t, time_t time, int depth ) {
  HEADER hdr, *h = &hdr;
  LIST *l;
  const char* cachekey = t;
  if( depth == 0 ) {
    cachekey = malloc( strlen( t ) + sizeof( "source:" ) );
    strcpy( ( char* )cachekey, "source:" );
    strcpy( ( char* )cachekey + 7, t );
  }
  if( !headerhash ) {
    headerhash = hashinit( sizeof( HEADER ), "headers" );
  }
  h->key = cachekey;
  h->includes = 0;
  h->time = time;
  h->headers = 0;
  h->newest = 0;
  if( !hashenter( headerhash, ( HASHDATA ** )&h ) ) {
    return h;
  }
  h->key = newstr( t );
  if( !cache_check( cachekey, time, &h->includes ) ) {
    h->includes = headers1( t, depth );
    cache_enter( cachekey, time, h->includes );
  }
  if( depth == 0 ) {
    free( ( char* )cachekey );
  }
  l = h->includes;
  while( l ) {
    const char *t2 = search( t, l->string, &time );
    if( time ) {
      h->headers = headerentry( h->headers, headersDepth( t2, time, depth + 1 ) );
    }
    l = list_next( l );
  }
  return h;
}

HEADER *headers( const char *t, time_t time ) {
  return headersDepth( t, time, 0 );
}

void headernewest( HEADER *h ) {
  HEADERS *hs = h->headers;
  if( h->newest ) {
    return;
  }
  h->newest = h;
  while( hs ) {
    headernewest( hs->header );
    if( hs->header->newest->time > h->newest->time ) {
      h->newest = hs->header->newest;
    }
    hs = hs->next;
  }
}

void donehdrs( void ) {
  my_redone( hdrre );
  hdrre = 0;
  my_redone( dimpre );
  dimpre = 0;
  hashdone( headerhash );
  headerhash = 0;
  alloc_free( hdralloc );
  hdralloc = 0;
}
